package com.jiunuan.live.living.provider.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.jiunuan.live.common.interfaces.dto.PageWrapper;
import com.jiunuan.live.common.interfaces.enums.CommonstatusEnum;
import com.jiunuan.live.common.interfaces.utils.ConvertBeanUtils;
import com.jiunuan.live.framework.redis.starter.key.LivingProviderCacheKeyBuilder;
import com.jiunuan.live.im.core.server.interfaces.dto.ImOfflineDTO;
import com.jiunuan.live.im.core.server.interfaces.dto.ImOnlineDTO;
import com.jiunuan.live.living.interfaces.dto.LivingRoomReqDTO;
import com.jiunuan.live.living.interfaces.dto.LivingRoomRespDTO;
import com.jiunuan.live.living.provider.dao.mapper.ILivingRoomMapper;
import com.jiunuan.live.living.provider.dao.po.LivingRoomPO;
import com.jiunuan.live.living.provider.service.ILivingRoomService;
import jakarta.annotation.Resource;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @Author: JiuNuan
 * @CreateTime: 2024-08-19 14:28
 * @Description:
 */
@Service
public class LivingRoomServiceImpl implements ILivingRoomService {
    @Resource
    private ILivingRoomMapper livingRoomMapper;
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    @Resource
    private LivingProviderCacheKeyBuilder cacheKeyBuilder;

    /**
     * 支持根据roomId查询出批量的userId（set）存储，3000个人，元素非常多，O(n)
     * 根据房间ID查询用户ID列表
     * 本方法通过查询Redis中存储的房间用户关系数据，来获取与特定房间关联的所有用户ID
     * 使用Redis的Set数据结构进行快速查询
     *
     * @param livingRoomReqDTO 请求对象，包含房间ID和应用ID
     * @return 用户ID列表，与房间关联的所有用户ID
     */
    @Override
    public List<Long> queryUserIdByRoomId(LivingRoomReqDTO livingRoomReqDTO) {
        // 获取房间ID
        Integer roomId = livingRoomReqDTO.getRoomId();
        // 获取应用ID
        Integer appId = livingRoomReqDTO.getAppId();
        // 构建Redis缓存键值，用于标识特定房间和应用的用户集合
        String cacheKey = cacheKeyBuilder.buildLivingRoomUserSet(roomId, appId);
        // 使用Redis的scan操作遍历集合，每次返回100个元素
        Cursor<Object> cursor = redisTemplate.opsForSet().scan(
                cacheKey,
                ScanOptions.scanOptions().match("*").count(100).build()
        );
        // 初始化用户ID列表，用于存储查询到的用户ID
        List<Long> userIdList = new ArrayList<>();
        // 遍历Redis集合中的每个元素
        while (cursor.hasNext()) {
            Integer userId = (Integer) cursor.next();
            userIdList.add(Long.valueOf(userId));
        }
        // 返回用户ID列表
        return userIdList;
    }


    /**
     * 用户上线处理
     *
     * @param imOnlineDTO
     */
    @Override
    public void userOnlineHandler(ImOnlineDTO imOnlineDTO) {
        Long userId = imOnlineDTO.getUserId();
        Integer appId = imOnlineDTO.getAppId();
        Integer roomId = imOnlineDTO.getRoomId();
        String cacheKey = cacheKeyBuilder.buildLivingRoomUserSet(roomId, appId);
        // set集合中
        redisTemplate.opsForSet().add(cacheKey, userId);
        redisTemplate.expire(cacheKey,12,TimeUnit.HOURS);
    }

    /**
     * 用户下线处理
     *
     * @param imOfflineDTO
     */
    @Override
    public void userOfflineHandler(ImOfflineDTO imOfflineDTO) {
        Long userId = imOfflineDTO.getUserId();
        Integer appId = imOfflineDTO.getAppId();
        Integer roomId = imOfflineDTO.getRoomId();
        String cacheKey = cacheKeyBuilder.buildLivingRoomUserSet(roomId, appId);
        redisTemplate.opsForSet().remove(cacheKey, userId);
    }

    /**
     * 根据直播间类型查询所有记录
     *
     * @param type
     * @return
     */
    @Override
    public List<LivingRoomRespDTO> listAllLivingRoomFromDB(Integer type) {
        LambdaQueryWrapper<LivingRoomPO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(LivingRoomPO::getStatus,CommonstatusEnum.VALID_STATUS.getCode());
        queryWrapper.eq(LivingRoomPO::getType, type);
        // 按时间顺序倒序展示，最多查询1000条记录
        queryWrapper.orderByDesc(LivingRoomPO::getId);
        queryWrapper.last("limit 1000");
        return ConvertBeanUtils.convertList(livingRoomMapper.selectList(queryWrapper), LivingRoomRespDTO.class);
    }

    /**
     * 直播间列表,分页查询
     *
     * @param livingRoomReqDTO 客厅查询参数，包含类型和分页信息
     * @return 返回封装了客厅响应数据的分页对象
     */
    @Override
    public PageWrapper<LivingRoomRespDTO> list(LivingRoomReqDTO livingRoomReqDTO) {
        // 构建直播间列表的缓存键，根据客厅类型
        String cacheKey = cacheKeyBuilder.buildLivingRoomList(livingRoomReqDTO.getType());
        // 获取当前页码和每页显示的数量
        int page = livingRoomReqDTO.getPage();
        int pageSize = livingRoomReqDTO.getPageSize();
        // 从Redis中获取列表的总大小，用于分页计算
        long total = redisTemplate.opsForList().size(cacheKey);
        // 根据分页信息，从Redis中获取相应的列表范围
        List<Object> resultList = redisTemplate.opsForList().range(cacheKey, (page - 1) * pageSize, (page * pageSize));
        // 创建分页包装类对象，用于返回分页数据
        PageWrapper<LivingRoomRespDTO> pageWrapper = new PageWrapper<>();

        // 如果结果列表为空，设置空列表和无下一页标志，并返回
        if(CollectionUtils.isEmpty(resultList)){
            pageWrapper.setList(Collections.emptyList());
            pageWrapper.setHasNext(false);
            return pageWrapper;
        } else{
            // 如果结果列表不为空，将列表元素转换为直播间响应DTO列表
            List<LivingRoomRespDTO> livingRoomRespDTOS = ConvertBeanUtils.convertList(resultList, LivingRoomRespDTO.class);
            // 设置转换后的列表和是否有下一页标志
            pageWrapper.setList(livingRoomRespDTOS);
            pageWrapper.setHasNext(total > (page * pageSize));
            return pageWrapper;
        }
    }

    /**
     * 根据房间号查询直播房间信息
     * 此方法主要用于根据房间号查询对应的直播房间信息，包括是否正在开播等状态
     * 通过设置查询条件，确保只有有效状态的房间信息被查询，避免返回无效或删除的房间信息
     * 最多返回一条记录，即对应房间号的最新状态
     *
     * @param roomId 房间号，用于定位特定的直播房间
     * @return 返回直播房间信息的DTO对象，包含房间状态等信息如果找不到对应的房间或房间状态无效，则返回null
     */
    @Override
    public LivingRoomRespDTO queryByRoomId(Integer roomId) {
        String cacheKey = cacheKeyBuilder.buildLivingRoomObj(roomId);
        LivingRoomRespDTO queryResult = (LivingRoomRespDTO) redisTemplate.opsForValue().get(cacheKey);
        if(queryResult != null){
            // 空值缓存
            if(queryResult.getId() == null){
                return null;
            }
            return queryResult;
        }
        // 创建查询包装器，用于设置查询条件
        LambdaQueryWrapper<LivingRoomPO>queryWrapper = new LambdaQueryWrapper<>();
        // 设置房间号为查询条件
        queryWrapper.eq(LivingRoomPO::getId,roomId);
        // 设置房间状态为有效，避免查询到无效或已删除的房间
        queryWrapper.eq(LivingRoomPO::getStatus,CommonstatusEnum.VALID_STATUS.getCode());
        // 设置查询限制，仅返回一条记录，即最新的有效状态
        queryWrapper.last("limit 1");
        // 执行查询，并将结果转换为LivingRoomRespDTO对象
        queryResult = ConvertBeanUtils.convert(livingRoomMapper.selectOne(queryWrapper),LivingRoomRespDTO.class);

        if(queryResult == null){
            // 防止缓存击穿
            redisTemplate.opsForValue().set(cacheKey,new LivingRoomRespDTO(),1, TimeUnit.MINUTES);
            return null;
        }
        return queryResult;
    }

    /**
     * 开始直播
     *
     * @param livingRoomReqDTO
     * @return 直播间id
     */
    @Override
    public Integer startLivingRoom(LivingRoomReqDTO livingRoomReqDTO) {
        LivingRoomPO livingRoomPo = ConvertBeanUtils.convert(livingRoomReqDTO, LivingRoomPO.class);
        livingRoomPo.setStatus(CommonstatusEnum.VALID_STATUS.getCode());
        livingRoomPo.setStartTime(new Date());
        livingRoomMapper.insert(livingRoomPo);

        String cacheKey = cacheKeyBuilder.buildLivingRoomObj(livingRoomPo.getId());
        // 防止之前有空值缓存，删除之
        redisTemplate.delete(cacheKey);
        return livingRoomPo.getId();
    }
}
